package de.arndt_plc.wakeonmagic;

import android.app.Activity;
import android.app.Fragment;
import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Color;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.Spinner;
import android.widget.TextView;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.sql.Struct;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;

/**
 * Created by MathiasAr on 23.10.2014.
 */
public class WakeOnMagicAppWidgetConfigureFragment extends Fragment {

    public static final String TAG = WakeOnMagicAppWidgetConfigureFragment.class.getSimpleName();

    private static final String ARG_WIDGET_ID = "widget_id";
    private static final String ARG_SHOW_ADVANCED = "show_advanced";
    private static final Integer[] SYMBOL_CHOICE = {R.drawable.ic_launcher};

    public static WakeOnMagicAppWidgetConfigureFragment newInstance(int widgetId, boolean showAdvanced) {
        WakeOnMagicAppWidgetConfigureFragment fragment = new WakeOnMagicAppWidgetConfigureFragment();
        Bundle args = new Bundle();
        args.putInt(ARG_WIDGET_ID, widgetId);
        args.putBoolean(ARG_SHOW_ADVANCED, showAdvanced);
        fragment.setArguments(args);
        return fragment;
    }

    EditText mAppWidgetPc;
    EditText mAppWidgetMac;
    EditText mAppWidgetIp;
    EditText mAppWidgetHost;
    ProgressBar mAppWidgetProgress;
    LinearLayout mAppWidgetAdv;
    Spinner mAppWidgetSymbol;
    ArrayAdapter<String> mIpListAdapter;
    Thread mReachableThread;
    private static final String PREFS_NAME = "de.arndt_plc.wakeonmagic.WakeOnMagicAppWidgetProvider";
    private static final String PREF_PREFIX_KEY = "appwidget_";
    private static final String PREF_SUFFIX_KEY_PC = "_pc";
    private static final String PREF_SUFFIX_KEY_MAC = "_mac";
    private static final String PREF_SUFFIX_KEY_IP_BROADCAST = "_ip";
    private static final String PREF_SUFFIX_KEY_HOST = "_host";
    private static final String PREF_SUFFIX_KEY_SYMBOL = "_symbol";

    private int mWidgetId;


    private interface OnReachableRunnableListener {
        public void OnHostReachable(InetAddress inetAddress, String mac);

        public void OnHostUnreachable(InetAddress inetAddress);
    }


    private class ReachableRunnable extends Thread implements Handler.Callback {

        final OnReachableRunnableListener mListener;
        final String mHost;
        InetAddress mInetAddress;
        final Handler mHandler;
        int mTimeout;

        public ReachableRunnable(OnReachableRunnableListener listener, String host, int timeout_ms) {
            mListener = listener;
            mHost = host;
            mInetAddress = null;
            mTimeout = timeout_ms;
            mHandler = new Handler(this);
        }

        @Override
        public void run() {
            boolean reachable = false;
            try {
                mInetAddress = InetAddress.getByName(mHost);
                if (!mInetAddress.isLoopbackAddress()) {
                    reachable = mInetAddress.isReachable(mTimeout);
                }
            } catch (Exception e) {
                Log.d(TAG, e.getMessage(), e);
            }
            if (!isInterrupted())
                mHandler.sendEmptyMessage(reachable ? 1 : 0);
            else
                Log.d(TAG, "Thread already interrupted... ignore address" + (mInetAddress != null ? " " + mInetAddress.toString() : ""));

        }

        @Override
        public boolean handleMessage(Message message) {
            if (mInetAddress != null && !isInterrupted()) {
                if (message.what == 0)
                    mListener.OnHostUnreachable(mInetAddress);
                else if (message.what == 1) {
                    // determine the MAC address

                    try {
                        Map<InetAddress, String> arpMap = IpHelper.getArpMap();
                        String mac = arpMap.get(mInetAddress);
                        mListener.OnHostReachable(mInetAddress, mac);
                    } catch (IOException e) {
                        Log.d(TAG, e.getMessage(), e);
                    }

                }
            }
            return true;
        }
    }


    @Override
    public View onCreateView(final LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // get arguments and activity
        Bundle args = getArguments();
        Activity activity = getActivity();
        View rootView;
        // check arguments
        if (args != null && (mWidgetId = args.getInt(ARG_WIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID)) != AppWidgetManager.INVALID_APPWIDGET_ID) {
            rootView = inflater.inflate(R.layout.wake_on_magic_app_widget_configure, container, false);

            mAppWidgetPc = (EditText) rootView.findViewById(R.id.appwidget_pc);
            mAppWidgetSymbol = (Spinner) rootView.findViewById(R.id.appwidget_symbol_select);
            mAppWidgetMac = (EditText) rootView.findViewById(R.id.appwidget_mac);
            mAppWidgetIp = (EditText) rootView.findViewById(R.id.appwidget_ip);
            mAppWidgetHost = (EditText) rootView.findViewById(R.id.appwidget_host);
            mAppWidgetProgress = (ProgressBar) rootView.findViewById(R.id.appwidget_progress);
            mAppWidgetProgress.setVisibility(View.GONE);
            mAppWidgetHost.setOnEditorActionListener(new TextView.OnEditorActionListener() {
                @Override
                public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) {
                    switch (actionId) {
                        case EditorInfo.IME_ACTION_SEARCH:
                            // interrupt running reachable thread
                            if (mReachableThread != null && mReachableThread.isAlive())
                                mReachableThread.interrupt();
                            // get text
                            String host = textView.getText().toString();
                            if (host != null && !host.isEmpty()) {
                                // start reachable thread
                                mReachableThread = new ReachableRunnable(new OnReachableRunnableListener() {
                                    @Override
                                    public void OnHostReachable(InetAddress inetAddress, String mac) {
                                        if (inetAddress.isLinkLocalAddress()) {
                                            Log.d(TAG, "Inet-Addr.: " + inetAddress.toString() + " is reachable and a local one (MAC: " + mac + ")");
                                            mAppWidgetHost.setText(inetAddress.getHostAddress());
                                            mAppWidgetHost.setSelection(mAppWidgetHost.length());
                                            mAppWidgetMac.setText(mac);
                                            InetAddress broadcast = getBroadCastIP(inetAddress);
                                            if (broadcast != null)
                                                mAppWidgetIp.setText(broadcast.getHostAddress());
                                            else
                                                mAppWidgetIp.setText("");
                                        } else {
                                            Log.d(TAG, "Inet-Addr.: " + inetAddress.toString() + " is reachable but not a local address");
                                            mAppWidgetHost.setTextColor(Color.GRAY);
                                            UiHelper.showKeyboard(mAppWidgetHost.getContext());
                                        }
                                        mAppWidgetProgress.setVisibility(View.GONE);
                                    }

                                    @Override
                                    public void OnHostUnreachable(InetAddress inetAddress) {
                                        Log.d(TAG, "Inet-Addr.: " + inetAddress.toString() + " is not reachable");
                                        mAppWidgetHost.setTextColor(Color.GRAY);
                                        UiHelper.showKeyboard(mAppWidgetHost.getContext());
                                        mAppWidgetProgress.setVisibility(View.GONE);
                                    }
                                }, host, 10000);
                                mReachableThread.start();
                            }
                            // hide the keyboard
                            UiHelper.hideKeyboard(mAppWidgetHost.getContext());
                            mAppWidgetProgress.setVisibility(View.VISIBLE);
                            return true;
                    }
                    return false;
                }
            });
            mAppWidgetHost.addTextChangedListener(new TextWatcher() {
                @Override
                public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {
                    mAppWidgetHost.setTextColor(mAppWidgetIp.getCurrentTextColor());
                    // interrupt running reachable thread
                    if (mReachableThread != null && mReachableThread.isAlive())
                        mReachableThread.interrupt();
                    mAppWidgetProgress.setVisibility(View.GONE);
                }

                @Override
                public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {

                }

                @Override
                public void afterTextChanged(Editable editable) {
                }
            });

            mAppWidgetAdv = (LinearLayout) rootView.findViewById(R.id.appwidget_advanced_settings);
            rootView.findViewById(R.id.add_button).setOnClickListener(mOnClickListener);
            CheckBox cbAdvanced = (CheckBox) rootView.findViewById(R.id.appwidget_show_advanced);
            cbAdvanced.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
                @Override
                public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
                    if (b)
                        mAppWidgetAdv.setVisibility(View.VISIBLE);
                    else
                        mAppWidgetAdv.setVisibility(View.GONE);
                }
            });
            // apply advanced settings state
            cbAdvanced.setChecked(args.getBoolean(ARG_SHOW_ADVANCED, false));

            Prefs prefs = load(activity, mWidgetId);

            mAppWidgetPc.setText(prefs.Pc);
            mAppWidgetMac.setText(prefs.Mac);
            mAppWidgetIp.setText(prefs.IpBroadcast);
            mAppWidgetHost.setText(prefs.Host);

            // set symbols
            ArrayAdapter<Integer> symAdapter = new ArrayAdapter<Integer>(getActivity(), R.layout.wake_on_magic_app_widget_symbol_list, SYMBOL_CHOICE) {
                @Override
                public View getView(int position, View convertView, ViewGroup parent) {
                    Integer resId = getItem(position);
                    if (convertView == null) {
                        // inflate
                        convertView = LayoutInflater.from(getContext()).inflate(R.layout.wake_on_magic_app_widget_symbol_list, null);
                    }
                    // set image
                    ImageView imgView = (ImageView) convertView.findViewById(R.id.appwidget_symbol_image);
                    imgView.setImageResource(resId);
                    // return updated/inflated view
                    return convertView;
                }

                @Override
                public View getDropDownView(int position, View convertView, ViewGroup parent) {
                    Integer resId = getItem(position);
                    if (convertView == null) {
                        // inflate
                        convertView = LayoutInflater.from(getContext()).inflate(R.layout.wake_on_magic_app_widget_symbol_list, null);
                    }
                    // set image
                    ImageView imgView = (ImageView) convertView.findViewById(R.id.appwidget_symbol_image);
                    imgView.setImageResource(resId);
                    // return updated/inflated view
                    return convertView;
                }
            };
            mAppWidgetSymbol.setAdapter(symAdapter);
            int pos = symAdapter.getPosition(prefs.Symbol);
            if (pos >= 0)
                mAppWidgetSymbol.setSelection(pos);

        } else
            rootView = inflater.inflate(R.layout.wake_on_magic_app_widget_configure_error, container, false);

        return rootView;
    }

    View.OnClickListener mOnClickListener = new View.OnClickListener() {
        public void onClick(View v) {
            Activity activity = getActivity();

            // When the button is clicked, store the string locally
            String widgetPc = mAppWidgetPc.getText().toString();
            String widgetMac = mAppWidgetMac.getText().toString();
            String widgetIp = mAppWidgetIp.getText().toString();
            String widgetHost = mAppWidgetHost.getText().toString();
            Integer widgetSymbol = (Integer) mAppWidgetSymbol.getSelectedItem();
            save(activity, mWidgetId, new Prefs(widgetPc, widgetMac, widgetIp, widgetHost, widgetSymbol));

            // It is the responsibility of the configuration activity to update the app widget
            AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(activity);
            WakeOnMagicAppWidgetProvider.updateAppWidget(activity, appWidgetManager, mWidgetId);

            // Make sure we pass back the original appWidgetId
            Intent resultValue = new Intent();
            resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, mWidgetId);
            activity.setResult(Activity.RESULT_OK, resultValue);
            activity.finish();
        }
    };

    public static class Prefs {
        public String Pc;
        public String Mac;
        public String IpBroadcast;
        public String Host;
        public Integer Symbol;

        public Prefs(String pc, String mac, String ipBroadcast, String host, Integer symbol) {
            Pc = pc;
            Mac = mac;
            IpBroadcast = ipBroadcast;
            Host = host;
            if (symbol == null)
                Symbol = R.drawable.ic_launcher;
            else
                Symbol = symbol;
        }
    }

    // Write the prefix to the SharedPreferences object for this widget
    static void save(Context context, int appWidgetId, Prefs preferences) {
        SharedPreferences.Editor prefs = context.getSharedPreferences(PREFS_NAME, 0).edit();
        prefs.putString(PREF_PREFIX_KEY + appWidgetId + PREF_SUFFIX_KEY_PC, preferences.Pc);
        prefs.putString(PREF_PREFIX_KEY + appWidgetId + PREF_SUFFIX_KEY_MAC, preferences.Mac);
        prefs.putString(PREF_PREFIX_KEY + appWidgetId + PREF_SUFFIX_KEY_IP_BROADCAST, preferences.IpBroadcast);
        prefs.putString(PREF_PREFIX_KEY + appWidgetId + PREF_SUFFIX_KEY_HOST, preferences.Host);
        prefs.putInt(PREF_PREFIX_KEY + appWidgetId + PREF_SUFFIX_KEY_SYMBOL, preferences.Symbol);
        prefs.apply();
    }

    // Read the prefix from the SharedPreferences object for this widget.
    // If there is no preference saved, get the default from a resource
    static Prefs load(Context context, int appWidgetId) {
        SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, 0);
        return (new Prefs(prefs.getString(PREF_PREFIX_KEY + appWidgetId + PREF_SUFFIX_KEY_PC, context.getString(R.string.appwidget_text)),
                prefs.getString(PREF_PREFIX_KEY + appWidgetId + PREF_SUFFIX_KEY_MAC, ""),
                prefs.getString(PREF_PREFIX_KEY + appWidgetId + PREF_SUFFIX_KEY_IP_BROADCAST, ""),
                prefs.getString(PREF_PREFIX_KEY + appWidgetId + PREF_SUFFIX_KEY_HOST, ""),
                prefs.getInt(PREF_PREFIX_KEY + appWidgetId + PREF_SUFFIX_KEY_SYMBOL, R.drawable.ic_launcher)));
    }

    static void delete(Context context, int appWidgetId) {
        SharedPreferences.Editor prefs = context.getSharedPreferences(PREFS_NAME, 0).edit();
        prefs.remove(PREF_PREFIX_KEY + appWidgetId + PREF_SUFFIX_KEY_PC);
        prefs.remove(PREF_PREFIX_KEY + appWidgetId + PREF_SUFFIX_KEY_MAC);
        prefs.remove(PREF_PREFIX_KEY + appWidgetId + PREF_SUFFIX_KEY_IP_BROADCAST);
        prefs.remove(PREF_PREFIX_KEY + appWidgetId + PREF_SUFFIX_KEY_HOST);
        prefs.remove(PREF_PREFIX_KEY + appWidgetId + PREF_SUFFIX_KEY_SYMBOL);
        prefs.apply();
    }

    static InetAddress getBroadCastIP(InetAddress inetAddress) {
        try {
            NetworkInterface nif = NetworkInterface.getByInetAddress(inetAddress);
            List<NetworkInterface> nis = new ArrayList<NetworkInterface>();
            if (nif == null) {
                Enumeration<NetworkInterface> allIf = NetworkInterface.getNetworkInterfaces();
                while (allIf.hasMoreElements())
                    nis.add(allIf.nextElement());

            } else
                nis.add(nif);

            for (NetworkInterface ni : nis) {
                if (ni != null && !ni.isLoopback()) {
                    for (InterfaceAddress interfaceAddress : ni.getInterfaceAddresses()) {
                        InetAddress broadcast = interfaceAddress.getBroadcast();
                        if (broadcast != null) {
                            byte[] bc = broadcast.getAddress();
                            byte[] ia = inetAddress.getAddress();
                            if (bc.length == ia.length) {
                                long bcVal = 0;
                                long iaVal = 0;
                                for (int i = 0; i < bc.length; ++i) {
                                    long pot = (bc.length - i - 1) * 8;
                                    bcVal += (bc[i] * Math.pow(2, pot));
                                    iaVal += (ia[i] * Math.pow(2, pot));
                                }
                                int shift = bc.length * 8 - interfaceAddress.getNetworkPrefixLength();
                                bcVal = bcVal >> (shift);
                                iaVal = iaVal >> (shift);
                                if (bcVal == iaVal) {
                                    Log.d(TAG, "Found IP: " + broadcast.toString() + " on interface " + ni.toString());
                                    return broadcast;
                                }
                            }
                        }
                    }
                }
            }
        } catch (SocketException e) {
            Log.d(TAG, e.getMessage(), e);
        }
        return null;
    }


}
